#include "net/tcp_client.hpp"
#include "base/logger.h"
#include <iostream>

using namespace easyasio::net;
using namespace easyasio::base;

bool is_no_delay = true;

#pragma pack(push,1)
struct EchoMessage { unsigned long long send_tv; int index; };
struct Message
{
    Message() { head = 0; memset(body, 0, sizeof body); }
    int bodylength() { return ntohl(head); }
    int packetlength() { return bodylength() + sizeof(int); }
    const char* peek() { return (const char*)&head; }
    void append(const char* msg, int len)
    {
        int current_len = bodylength();
        memcpy(body, msg, len);
        current_len += len;
        head = htonl(current_len);
    }

    int head;
    char body[16 * 1024];
};
#pragma pack(pop)

class TestClient
{
public:
    TestClient(asio::io_service& io_service, const std::string remote_addr, int port, int blocksize, int internalmsec, int testtime) :
        io_loop_(io_service),
        remote_addr_(remote_addr),
        port_(port),
        blocksize_(blocksize),
        internal_(internalmsec),
        test_time_sec_(testtime),
        loop_timer_(io_service),
        end_timer_(io_service)
    {
        using namespace std::chrono;
        begin_ =  
            (duration_cast<milliseconds>(system_clock::now().time_since_epoch())).count();

         client_ = std::make_shared<TcpClient>(io_service, remote_addr.c_str(), port);
         client_->setConnectionCallback(std::bind(&TestClient::connectionCallback, this, std::placeholders::_1));
         client_->setMessageCallback(
             std::bind(&TestClient::messageCallback, this, std::placeholders::_1, std::placeholders::_2));
         client_->asynConnect();

        end_timer_.expires_from_now(std::chrono::seconds(test_time_sec_));
	    end_timer_.async_wait(std::bind(&TestClient::handleQuitTimter, this));
    }

    void handleQuitTimter()
    {
        if (!conn_)
            return;
        std::cout << "client quit" << std::endl;
        loop_timer_.cancel();
        conn_->forceClose();
        io_loop_.stop(); 
    }

    void setTimer(int milliseconds)
	{
        loop_timer_.expires_from_now(std::chrono::milliseconds(milliseconds));
	    loop_timer_.async_wait(std::bind(&TestClient::handleTimter, this));
	}

    void handleTimter()
    {
        setTimer(internal_);
        sendMsg();
    }

    void sendMsg()
    {
        if (!conn_)
            return;
        Message msg = MakeMessage();
        conn_->send(std::string(msg.peek(), msg.packetlength()));
    }

    void start()
    {
    	io_loop_.run();
    }

    Message MakeMessage()
    {
        static int index = 0;
        std::string packet(blocksize_, 't');
        EchoMessage echo;
        echo.index = index++;
        echo.send_tv = (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())).count(); 
        memcpy(&packet[0], &echo, sizeof(echo));
        Message msg;
        msg.append(packet.c_str(), packet.size());
        return msg;
    }

    ~TestClient(){}

    void closeCallback(const TcpConnectionPtr& cn)
    {
        conn_.reset();
    }

    void connectionCallback(const TcpConnectionPtr& cn)
    {
        setTimer(internal_);
        cn->setTcpNoDelay(is_no_delay);
        cn->setCloseCallback(std::bind(&TestClient::closeCallback, this, std::placeholders::_1));
        conn_ = cn;
        sendMsg();
    }

    void onMessageArrive(const char* msg, int len)
    {
        EchoMessage* ep = (EchoMessage*)msg;
        unsigned long long current = 
            (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())).count();

        printf("%0.3f %d\n", (current - begin_) / 1000.0, (current - ep->send_tv));
    }

    void messageCallback(const TcpConnectionPtr&, Buffer* buf)
    {
        while (1)
        {
            if (buf->readableBytes() < sizeof (int))
                break;
            int packet_len = ntohl(*((int*)(buf->peek())));
            int total_len = sizeof(int) + packet_len;

            if (buf->readableBytes() < total_len)
                break;

            buf->retrieve(sizeof (int));
            onMessageArrive(buf->peek(), packet_len);
            buf->retrieve(packet_len);
        }
    }

private:
    asio::io_service& io_loop_;
    std::string remote_addr_;
    int port_;
    int blocksize_;
    int internal_;
    int test_time_sec_;

    asio::basic_waitable_timer<std::chrono::system_clock> loop_timer_;
    asio::basic_waitable_timer<std::chrono::system_clock> end_timer_;
    unsigned long long begin_;
    std::shared_ptr<TcpClient> client_;
    TcpConnectionPtr conn_;
};

int main(int argc, char* argv[])
{
    if (argc != 7)
    {
        printf("usage: tcp_delay_test_client remote_addr remote_port blocksize internal no_delay[1|0] testtimesec\n");
        exit(-1);
    }
    try
    {
        Logger::instance().setLevel(Logger::LOGLEVEL_TRACE);
        asio::io_service loop;

        is_no_delay = atoi(argv[5]) ? true : false;

        TestClient client(loop, argv[1], atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), atoi(argv[6]));
        client.start();
    }
    catch (asio::system_error& e)
    { 
        LOG_ERROR("{}", e.what());
    }
    catch (...)
    {
        LOG_ERROR("unknow error");
    }

    return 0;
}